﻿using System;
using System.Diagnostics;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Runtime.InteropServices;
using Microsoft.Kinect;
using Coding4Fun.Kinect.Wpf;
using DPoint = System.Drawing.Point;

// Project defined usings
using PowerPoint_Control;
using Mouse_Control;
using Kinect_Interface_Prototype_1.Interface_Design;
using Folder_Control;
using Kinect_Interface_Prototype_1.Voice_Commands;

namespace Kinect_Interface_Prototype_1
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }

        //public static List<FolderControlForm> FolderControlForms = new List<FolderControlForm>();       
        //private ControlForm controlForm;
        //private PowerpointControl powerPointControl;
        //VoiceApp voiceApp;

        public enum ImageType {Color, Depth}

        private IniFile settingsINI = new IniFile(Constants.SettingsINI);

        private const float SkeletonMaxX = 0.60f;
        private const float SkeletonMaxY = 0.40f;

        private KinectSensor kinectSensor;
        private Brush brush = new SolidColorBrush(Color.FromRgb(128, 128, 255));
        private int totalFrames = 0;
        private int lastFrames = 0;
        private DateTime lastTime = DateTime.MaxValue;

        private bool MouseControlOn = true;

        private Dictionary<JointType, Brush> jointColours = new Dictionary<JointType, Brush>()
        {
            { JointType.HipCenter, new SolidColorBrush(Color.FromRgb(169, 176, 155)) },
            { JointType.Spine, new SolidColorBrush(Color.FromRgb(169, 176, 155)) },
            { JointType.ShoulderCenter, new SolidColorBrush(Color.FromRgb(168, 230, 29)) },
            { JointType.Head, new SolidColorBrush(Color.FromRgb(200, 0,   0)) },
            { JointType.ShoulderLeft, new SolidColorBrush(Color.FromRgb(79,  84,  33)) },
            { JointType.ElbowLeft, new SolidColorBrush(Color.FromRgb(84,  33,  42)) },
            { JointType.WristLeft, new SolidColorBrush(Color.FromRgb(255, 126, 0)) },
            { JointType.HandLeft, new SolidColorBrush(Color.FromRgb(215,  86, 0)) },
            { JointType.ShoulderRight, new SolidColorBrush(Color.FromRgb(33,  79,  84)) },
            { JointType.ElbowRight, new SolidColorBrush(Color.FromRgb(33,  33,  84)) },
            { JointType.WristRight, new SolidColorBrush(Color.FromRgb(77,  109, 243)) },
            { JointType.HandRight, new SolidColorBrush(Color.FromRgb(37,   69, 243)) },
            { JointType.HipLeft, new SolidColorBrush(Color.FromRgb(77,  109, 243)) },
            { JointType.KneeLeft, new SolidColorBrush(Color.FromRgb(69,  33,  84)) },
            { JointType.AnkleLeft, new SolidColorBrush(Color.FromRgb(229, 170, 122)) },
            { JointType.FootLeft, new SolidColorBrush(Color.FromRgb(255, 126, 0)) },
            { JointType.HipRight, new SolidColorBrush(Color.FromRgb(181, 165, 213)) },
            { JointType.KneeRight, new SolidColorBrush(Color.FromRgb(71, 222,  76)) },
            { JointType.AnkleRight, new SolidColorBrush(Color.FromRgb(245, 228, 156)) },
            { JointType.FootRight, new SolidColorBrush(Color.FromRgb(77,  109, 243)) }
        };

        //Use to transform and reduce jitter
        private TransformSmoothParameters TransformParameters = new TransformSmoothParameters
        {
            Smoothing = 0.1f,
            Correction = 0.3f,
            Prediction = 0.4f,
            JitterRadius = 1.0f,
            MaxDeviationRadius = 0.5f
        };

        private void Window_Loaded(object sender, RoutedEventArgs e)
        {
            try
            {
                foreach (KinectSensor sensor in KinectSensor.KinectSensors)
                {
                    if (sensor.Status == KinectStatus.Connected)
                    {
                        // Pass parameters to set smoothing
                        sensor.SkeletonStream.Enable(TransformParameters);
                        sensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
                        sensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30);
                        sensor.AllFramesReady += new EventHandler<AllFramesReadyEventArgs>(KinectAllFramesReady);
                        sensor.DepthStream.Range = DepthRange.Default;
                        sensor.Start();

                        this.kinectSensor = sensor;

                        break;
                    }
                }
            }
            catch (InvalidOperationException)
            {
                MessageBox.Show("Runtime initialization failed. Ensure Kinect is plugged in");
                return;
            }


            //MouseControlOn = Boolean.Parse(settingsINI.IniReadValue("General", "mouseControlOnStart"));
            //voiceApp = new VoiceApp(this, Boolean.Parse(settingsINI.IniReadValue("General", "voiceCommandsStart")));
            //ToggleShowSkeletalWindow(Boolean.Parse(settingsINI.IniReadValue("General", "showSkeletalTracking")));

            //controlForm = new ControlForm(this);
            //controlForm.Show();
            //controlForm.SwitchOn(OnOffControlType.Kinect);

            this.lastTime = DateTime.Now;
        }

        Skeleton[] skeletons;

        private void KinectAllFramesReady(object sender, AllFramesReadyEventArgs e)
        {
            using (SkeletonFrame skeletonFrame = e.OpenSkeletonFrame())
            {
                this.skeletonCanvas.Children.Clear();

                bool receivedData = false;

                if (skeletonFrame != null)
                {
                    if (skeletons == null) //allocate the first time
                    {
                        skeletons = new Skeleton[skeletonFrame.SkeletonArrayLength];
                    }

                    skeletonFrame.CopySkeletonDataTo(skeletons);

                    receivedData = true;
                }

                if (receivedData)
                {
                    // Gets closest skeleton (Only tracks one)
                    List<Skeleton> trackedSkeletons = skeletons.Where(s => s != null && s.TrackingState == SkeletonTrackingState.Tracked).ToList();
                    Skeleton skeletalData = trackedSkeletons.Count > 0 ? trackedSkeletons.Aggregate((closest, s) => (closest == null || (s != null && (s.Position.Z) < closest.Position.Z) ? s : closest)) : null;

                    // If no tracked skeleton, switch skeletal tracking to off and return.
                    if (skeletalData == null)
                    {
                        //controlForm.SwitchOff(OnOffControlType.SkeletalTracking);
                        return;
                    }

                    // If the skeletal tracking switch isn't already on, switch it to on
                    //if (!controlForm.GetSwitchOn(OnOffControlType.SkeletalTracking))
                    //    controlForm.SwitchOn(OnOffControlType.SkeletalTracking);

                    using (DepthImageFrame depthImageFrame = e.OpenDepthImageFrame())
                    {
                        if (depthImageFrame != null)
                        {
                            // Draw bones
                            this.skeletonCanvas.Children.Add(this.getBodySegment(depthImageFrame, skeletalData, brush, JointType.HipCenter, JointType.Spine, JointType.ShoulderCenter, JointType.Head));
                            this.skeletonCanvas.Children.Add(this.getBodySegment(depthImageFrame, skeletalData, brush, JointType.ShoulderCenter, JointType.ShoulderLeft, JointType.ElbowLeft, JointType.WristLeft, JointType.HandLeft));
                            this.skeletonCanvas.Children.Add(this.getBodySegment(depthImageFrame, skeletalData, brush, JointType.ShoulderCenter, JointType.ShoulderRight, JointType.ElbowRight, JointType.WristRight, JointType.HandRight));

                            var drawJoints = new List<Joint>(){skeletalData.Joints[JointType.HipCenter], skeletalData.Joints[JointType.Spine], skeletalData.Joints[JointType.ShoulderCenter], skeletalData.Joints[JointType.Head], skeletalData.Joints[JointType.ShoulderLeft],
                                                skeletalData.Joints[JointType.ElbowLeft], skeletalData.Joints[JointType.WristLeft], skeletalData.Joints[JointType.HandLeft], skeletalData.Joints[JointType.ShoulderRight], skeletalData.Joints[JointType.ElbowRight], 
                                                skeletalData.Joints[JointType.WristRight], skeletalData.Joints[JointType.HandRight]};

                            // Draw joints
                            foreach (Joint joint in drawJoints)
                            {
                                //short depth;
                                Point jointPos = GetPosition2DLocation(depthImageFrame, joint.Position);
                                Line jointLine = new Line();
                                jointLine.X1 = jointPos.X - 3;
                                jointLine.X2 = jointLine.X1 + 6;
                                jointLine.Y1 = jointLine.Y2 = jointPos.Y;
                                jointLine.Stroke = jointColours[joint.JointType];
                                jointLine.StrokeThickness = 6;

                                this.skeletonCanvas.Children.Add(jointLine);
                            }

                            Joint rightHand = skeletalData.Joints[JointType.HandRight];
                            Joint leftHand = skeletalData.Joints[JointType.HandLeft];
                            Joint sholderLeft = skeletalData.Joints[JointType.ShoulderLeft];
                            Joint hipLeft = skeletalData.Joints[JointType.HipLeft];

                            float minDepth = Math.Min(rightHand.Position.Z, Math.Min(leftHand.Position.Z, Math.Min(sholderLeft.Position.Z, hipLeft.Position.Z))) * 1000;

                            Point rightHandPoint = GetPosition2DLocation(depthImageFrame, rightHand.Position);
                            Point leftHandPoint = GetPosition2DLocation(depthImageFrame, skeletalData.Joints[JointType.HandLeft].Position);
                            Point shoulderLeftPoint = GetPosition2DLocation(depthImageFrame, skeletalData.Joints[JointType.ShoulderLeft].Position);
                            Point hipLeftPoint = GetPosition2DLocation(depthImageFrame, skeletalData.Joints[JointType.HipLeft].Position);

                            Process[] powerpoint = Process.GetProcessesByName("POWERPNT");

                            if (powerpoint.Length == 0 && MouseControlOn)
                            {
                                MouseControl.UpdateMouse(rightHand, leftHandPoint, shoulderLeftPoint, hipLeftPoint, minDepth, SkeletonMaxX, SkeletonMaxY);

                                HandDepth.Text = rightHand.Position.Z * 1000 + " mm";
                                XPosition.Text = rightHandPoint.X.ToString();
                                YPosition.Text = rightHandPoint.Y.ToString();
                            }
                            else
                            {
                                //if (powerPointControl == null)
                                //    powerPointControl = new PowerpointControl();

                                //powerPointControl.DetermineAction(leftHandPoint, rightHandPoint);
                            }
                        }
                    }
                }
            }

            this.CalculateFps();
        }

        private Polyline getBodySegment(DepthImageFrame depthFrame, Skeleton skeletalData, Brush brush, params JointType[] jointTypes)
        {
            PointCollection points = new PointCollection(jointTypes.Length);
            foreach(var jointType in jointTypes)
            {
                points.Add(GetPosition2DLocation(depthFrame, skeletalData.Joints[jointType].Position));
            }
            return new Polyline()
            {
                Points = points,
                Stroke = brush,
                StrokeThickness = 5
            };
        }

        private Point GetPosition2DLocation(DepthImageFrame depthFrame, SkeletonPoint skeletonPoint)
        {
            ImageType ImageType = ImageType.Depth;

            DepthImagePoint depthPoint = depthFrame.MapFromSkeletonPoint(skeletonPoint);

            switch (ImageType)
            {
                case ImageType.Color:
                    ColorImagePoint colorPoint = depthFrame.MapToColorImagePoint(depthPoint.X, depthPoint.Y, kinectSensor.ColorStream.Format);

                    // map back to skeleton.Width & skeleton.Height
                    return new Point(
                        (int)(this.RenderSize.Width * colorPoint.X / kinectSensor.ColorStream.FrameWidth),
                        (int)(this.RenderSize.Height * colorPoint.Y / kinectSensor.ColorStream.FrameHeight));
                case ImageType.Depth:
                    double depthX = Math.Max(0, Math.Min(skeletonCanvas.Width * depthPoint.X / depthFrame.Width, skeletonCanvas.Width));
                    double depthY = Math.Max(0, Math.Min(skeletonCanvas.Height * depthPoint.Y / depthFrame.Height, skeletonCanvas.Height));
                    return new Point((int)depthX, (int)depthY);
                default:
                    throw new ArgumentOutOfRangeException("ImageType was a not expected value: " + ImageType.ToString());
            }
        }

        private void CalculateFps()
        {
            this.totalFrames++;
            DateTime current = DateTime.Now;
            if (current.Subtract(this.lastTime) > TimeSpan.FromSeconds(1))
            {
                int frameDifference = this.totalFrames - this.lastFrames;
                this.lastFrames = this.totalFrames;
                this.lastTime = current;
                this.fps.Text = frameDifference.ToString() + " fps";
            }
        }

        private void Window_Closed(object sender, EventArgs e)
        {
            if (kinectSensor != null) kinectSensor.Stop();
            Environment.Exit(0);
        }


        // Public Functions
        public void adjustElevation(int elevation)
        {
            if (elevation > kinectSensor.MinElevationAngle && elevation < kinectSensor.MaxElevationAngle)
            {
                kinectSensor.ElevationAngle = elevation;
            }
        }

        public void ToggleShowSkeletalWindow(bool show)
        {
            if (show) this.Show();
            else this.Hide();
        }

        public void ShutDown()
        {
            if(kinectSensor != null) kinectSensor.Stop();
            Environment.Exit(0);
        }

        public void ToggleVoiceCommands(bool active)
        {
            //voiceApp.VoiceCommandsActive = active;
        }

        public void ToggleVoiceCommandsSwitch(bool On)
        {
            //controlForm.ToggleSwitch(OnOffControlType.VoiceCommands, On);
        }

        public void ToggleMouseControl(bool active)
        {
            MouseControlOn = active;
        }
    }
}
